package uk.co.q3c.deplan.server.util.rules;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.List;
import java.util.logging.Logger;

import javax.persistence.Id;

import uk.co.q3c.deplan.client.domain.AbstractDomainObject;
import uk.co.q3c.deplan.client.domain.DomainKind;
import uk.co.q3c.deplan.client.domain.DomainObject;
import uk.co.q3c.deplan.client.domain.resource.PersistenceReferenceException;
import uk.co.q3c.deplan.server.util.InvalidPropertyException;
import uk.co.q3c.deplan.server.util.useraccess.UserAccessController;
import uk.co.q3c.deplan.server.util.validation.Validator;
import uk.co.q3c.deplan.server.util.validation.rules.PropertyValuer;

import com.googlecode.objectify.Key;

/**
 * Contains a set of rules to configure an {@link AutoEditor} for a specified class, as defined by {@link #className}. Rules are for: <li>
 * Layout, field order and widget type ({@link LayoutOptionManager})<li>
 * Display format of individual properties ({@link PropertyFormatter})<li>
 * User access rules ({@link UserAccessController}) <li>
 * Validation rules ({@link Validator})<br>
 * <br>
 * Most rules can be for a property or for an object (where the rule spans more than one property). Properties and objects can each have
 * multiple rules.
 * 
 * @author DSowerby 22 Apr 2009
 * 
 */
public class AutoEditorRules extends AbstractDomainObject implements PropertyValuer, Serializable {
	private static final long serialVersionUID = -2203842793892116105L;
	transient protected final Logger logger = Logger.getLogger(getClass().getName());
	private String className;
	private Validator validator;
	private UserAccessController userAccessController;
	// private LayoutOptionManager layoutManager;
	private PropertyFormatter propertyFormatter;
	private transient AbstractDomainObject target;
	private transient List<String> validPropertyNames;

	@Id
	private Long keyId;

	public AutoEditorRules() {
		super();
	}

	@Override
	protected void beforeLoad() {
		super.beforeLoad();
		if (validator == null) {
			validator = new Validator(this);
		}
		if (userAccessController == null) {
			userAccessController = new UserAccessController(this);
		}

		if (propertyFormatter == null) {
			propertyFormatter = new PropertyFormatter();
		}
	}

	public String getClassName() {

		return className;
	}

	public void setClassName(String className) {

		String oldValue = this.className;
		this.className = className;
		firePropertyChange("className", oldValue, this.className);
	}

	public Validator validator() {

		return validator;
	}

	public UserAccessController userAccessController() {

		return userAccessController;
	}

	// public LayoutOptionManager layoutManager() {
	//
	// return layoutManager;
	// }

	public Object propertyValue(String propertyName) {
		String prop = Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1);
		String methodName = "get" + prop;
		Class<?>[] types = new Class[] {};
		Method method = null;
		Object result = null;
		try {
			method = target.getClass()
					.getMethod(methodName, types);
			result = method.invoke(target, new Object[0]);
		} catch (Exception e) {
			logger.severe("Error binding property " + propertyName + e.getStackTrace());
			return null;
		}
		return result;
	}

	public AbstractDomainObject getTarget() {
		return target;
	}

	public void setTarget(AbstractDomainObject target) {
		this.target = target;
	}

	@Override
	public boolean hasProperty(String propertyName) {
		return validPropertyNames.contains(propertyName);
	}

	@Override
	public List<String> validPropertyNames() {
		return validPropertyNames;
	}

	@Override
	public void setValidPropertyNames(List<String> validPropertyNames) {
		this.validPropertyNames = validPropertyNames;
	}

	public PropertyFormatter propertyFormatter() {
		return propertyFormatter;
	}

	/**
	 * When an object contains another object, a property naming structure similar to that for class names is used, for example: objectA
	 * contains an embedded objectB, which has a property named 'widgetName', the full property descriptor would be objectB.widgetName. <br>
	 * <br>
	 * Call is ignored if <code>subPropertyNames</code> is null.
	 * 
	 * @param primaryPropertyName
	 * @param subPropertyNames
	 * @throws InvalidPropertyException
	 *             if <code>primaryPropertyName</code> is not already in {@link #validPropertyNames}
	 */
	public void addSubPropertyNames(String primaryPropertyName, List<String> subPropertyNames) throws InvalidPropertyException {
		if (subPropertyNames == null) {
			return;
		}
		if (!validPropertyNames.contains(primaryPropertyName)) {
			String msg = primaryPropertyName + " is not a valid property name";
			logger.severe(msg);
			throw new InvalidPropertyException(msg);
		}
		for (String subName : subPropertyNames) {
			String s = primaryPropertyName + "." + subName;
			validPropertyNames.add(s);
		}
	}

	@Override
	public DomainKind getKind() {
		throw new RuntimeException("not yet implemented");
	}

	public Long getKeyId() {
		return keyId;
	}

	@Override
	public Key<? extends DomainObject> getKey() throws PersistenceReferenceException {
		throw new RuntimeException("Not yet implemented");
	}

	@Override
	public void setKeyId(Long keyId) {
		throw new RuntimeException("Not yet implemented");
	}
}
